'''
樽海鞘群优化算法
'''
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import math
import random
import os
import time

class SSA:
    '''
    初始化种群坐标
    '''
    def __init__(self,D=2, N=10, M=100,p_low=[0], p_up=[50],obj_func=None):
        self.D = D # 粒子维度
        self.N = N # 粒子群规模，初始化种群个数
        self.M = M # 最大迭代次数
        self.p_range = [p_low, p_up]  # 粒子位置的约束范围
        self.obj_func = obj_func
        #使用numpy初始化全体坐标为0
    
        self.pos = np.zeros((N, D+1))
        #随机初始化种群坐标
        for i in range(N):
            for j in range(D):
                self.pos[i,j] = random.uniform(self.p_range[0][0], self.p_range[1][0])  
            #  生成的第i个可变坐标的目标函数值，并将其存储在最后一列
            self.pos[i,-1] = obj_func(self.pos[i,0:-1])

    '''
    初始化食物位置
    '''
    def InitialFood(self):
    
        # 初始化食物位置为0
    
        self.fpos = np.zeros((1, self.D+1))
    
    
        # 为所有维度赋值（最后一列除外）
        for j in range(self.D):
            self.fpos[0,j] = 0.0
    
        # 计算该坐标的目标值
        self.fpos[0,-1] = self.obj_func(self.pos[0,0:-1])
        #print(f'初始化食物坐标{self.fpos}')
    
    '''
    更新食物位置
    '''
    def updateFood(self):
        fit_arr = []
    
        for i in range(self.pos.shape[0]):
            fit_arr.append(self.pos[i,-1])
            #print(f'第{i}更新食物位置中：{fpos[0,-1]}和{pos[i,-1]}')
            if (self.fpos[0,-1] > self.pos[i,-1]):
            
                # 使用樽海鞘坐标更新食物坐标
                for j in range(self.pos.shape[1]):
                    self.fpos[0,j] = self.pos[i,j]
        return fit_arr
    
    '''
    更新樽海鞘位置
    '''
    def update_position(self,c1):
        n = self.N
        dimensions = self.D
        xmax = self.p_range[1][0]
        xmin = self.p_range[0][0]
        #开始更新坐标
    
        for i in range(n):
        
            if(i<=n/2):
            
            
                #更新第i个变量的维度
            
                for j in range(dimensions):
                
                
                
                    # 随机生成c2、c3
                    c2 = int.from_bytes(os.urandom(8), byteorder = "big") / ((1 << 64) - 1)
                    c3 = int.from_bytes(os.urandom(8), byteorder = "big") / ((1 << 64) - 1)
                
                    if (c3 >= 0.5):
                    
                        # 这里的clip函数用于映射搜索空间之外的坐标
                    
                        self.pos[i,j] = np.clip((self.fpos[0,j] + c1*((xmax - xmin)*c2 + xmin)), xmin, xmax)
                    else:
                    
                        self.pos[i,j] = np.clip((self.fpos[0,j] - c1*((xmax - xmin)*c2 + xmin)), xmin, xmax)
                    
            
            
            elif(i > n/2 and i < n + 1):
            
                for j in range(dimensions):
                
                    # 第i个和第i-1个变量坐标的平均值
                    self.pos[i,j] = np.clip(((self.pos[i - 1,j] + self.pos[i,j])/2), xmin, xmax) 
                
                
            # 计算此更新坐标的目标值
            self.pos[i,-1] = self.obj_func(self.pos[i,0:-1])         
        
    
    def ssa(self):
        #最佳适应度集合
        best_fit=[]
        avg_fit = []
        avg_ts = []#平均方差
        
        L = self.M
        
    
        # 初始化当前全局最优的食物位置
        self.InitialFood()

        # 迭代
        time_start = time.time() #记录迭代寻优开始时间
        for l in range(L): 
            c1 = 2*math.exp(-(4*(l/L))**2)
        
            # 更新食物位置
            fit_arr =  self.updateFood()
            avg_fit.append(np.mean(fit_arr))
            avg_ts.append(np.var(fit_arr))
            # print(f'第{l}代，食物为{self.fpos}')
        
            #绘制适应曲线
            best_fit.append(self.fpos[0,-1])
            
        
        
            # 更新樽海鞘位置
            self.update_position(c1)

            # print(f'第{l}代，樽海鞘为{self.pos}')
        time_end = time.time() #记录迭代结束时间
        print(f'SSA共花费 {time_end - time_start} 秒')

            
        return best_fit,avg_fit,avg_ts



#基准函数测试
def sphereModel(variables_values = [0,0]):
    func_value = 0
    
    for i in range(len(variables_values)):
        func_value += variables_values[i]**2
    return func_value



# if __name__ == '__main__':
#     low = [-5.12, -5.12, -5.12, -5.12, -5.12]
#     up = [5.12, 5.12, 5.12, 5.12, 5.12]
#     ssa = SSA(2,10,50,low,up,sphereModel)
#     ssa.ssa()



